/**
 *    Copyright (C) 2007-2009 DIVIS GmbH (http://www.divis-hagen.de/)
 * 
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 3 of the License, or (at your option) any later version.
 * 
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 * 
 *    You should have received a copy of the GNU Lesser General
 *    Public License along with this library; if not, write to the
 *    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *    Boston, MA 02110-1301 USA
 */

package de.divis_hagen.json;

import de.divis_hagen.json.annotation.JSONCollectionResource;
import de.divis_hagen.json.annotation.JSONFormat;
import de.divis_hagen.json.annotation.JSONId;
import de.divis_hagen.json.annotation.JSONReadOnly;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.AbstractCollection;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlValue;




/**
 *
 * @author Aike J Sommer
 */
public class JSONContextImpl extends JSONContext {

    private boolean unwrap = false;
    private boolean escapeNames = true;
    private final Map<Class, TypeMeta> meta = new HashMap<Class, TypeMeta>();
    private final Set<TypeMeta.Complex> rootTypes = new HashSet<TypeMeta.Complex>();
    private final Set<TypeMeta.Complex> complexTypes = new HashSet<TypeMeta.Complex>();
    private final Set<TypeMeta.Primitive> enumTypes = new HashSet<TypeMeta.Primitive>();

    public JSONContextImpl(Class[] classes) {
        readMeta(classes);
    }

    public <Type> TypeMeta<Type> meta(Class type) {
        if (meta.containsKey(type)) {
            return meta.get(type);
        }
        throw new IllegalArgumentException(type.getName());
    }

    private void readMeta(Class[] classes) {
        Collection<Class> allClasses = findClasses(classes);
        Map<Class, Collection<Class>> subTypes = findSubTypes(allClasses);
        for (Class c : allClasses) {
            createMeta(c);
        }
        for (Class c : allClasses) {
            buildMeta(c, subTypes.get(c));
        }
//        for (TypeMeta m : meta.values()) {
//            unwrapProperties(m);
//        }
    }

    private void createMeta(Class c) {
        if (meta.containsKey(c)) {
            return;
        }

        TypeMeta m = TypeMeta.primitive(c);
        if (m == null) {
            if (! Object.class.isAssignableFrom(c)) {
                throw new IllegalArgumentException(c.getName());
            }
            m = new TypeMeta.ComplexBase<Object>(c, c.isAnnotationPresent(XmlRootElement.class),
                    c.isAnnotationPresent(JSONCollectionResource.class));
        }
        meta.put(c, m);
        if (m instanceof TypeMeta.Complex) {
            complexTypes.add((TypeMeta.Complex) m);
            if (((TypeMeta.Complex) m).isRootType()) {
                rootTypes.add((TypeMeta.Complex) m);
            }
        } else if ((m instanceof TypeMeta.Primitive) && ((TypeMeta.Primitive) m).isEnumType()) {
            enumTypes.add((TypeMeta.Primitive) m);
        }
    }

    private void buildMeta(Class c, Collection<Class> subTypes) {
        if (meta.get(c).isPrimitive()) {
            return;
        }

        TypeMeta.Complex<Object> m = (TypeMeta.Complex) meta.get(c);
        Set<String> names = new HashSet<String>();
        for (Field field : c.getDeclaredFields()) {
            names.add(field.getName());
        }
        for (Method method : c.getDeclaredMethods()) {
            String methodName = method.getName();
            if (methodName.startsWith("get") && method.getParameterTypes().length == 0 &&
                    ! method.getReturnType().equals(Void.TYPE)) {
                names.add(nameFromMethod(methodName));
            }
        }

        for (String property : names) {
            Field field;
            try {
                field = c.getDeclaredField(property);
            } catch (NoSuchFieldException ex) {
                field = null;
            }
            XmlElement fieldXmlEle = field == null ? null : field.getAnnotation(XmlElement.class);
            XmlAttribute fieldXmlAttr = field == null ? null : field.getAnnotation(XmlAttribute.class);
            XmlValue fieldXmlValue = field == null ? null : field.getAnnotation(XmlValue.class);
            JSONElement fieldJSONEle = field == null ? null : field.getAnnotation(JSONElement.class);
            XmlTransient fieldXmlTrans = field == null ? null : field.getAnnotation(XmlTransient.class);
//            JSONTransient fieldJSONTrans = field == null ? null : field.getAnnotation(JSONTransient.class);
//            Set<RequestType> fieldJSONTransRequests = fieldJSONTrans == null ? Collections.EMPTY_SET :
//                EnumSet.copyOf(Arrays.asList(fieldJSONTrans.request()));
            JSONReadOnly fieldJSONReadOnly = field == null ? null : field.getAnnotation(JSONReadOnly.class);
            JSONId fieldJSONId = field == null ? null : field.getAnnotation(JSONId.class);
            JSONFormat fieldJSONFormat = field == null ? null : field.getAnnotation(JSONFormat.class);
            boolean fieldEle = fieldJSONEle != null || fieldXmlEle != null
                    || fieldXmlAttr != null || fieldXmlValue != null;
            boolean fieldTrans = fieldXmlTrans != null;// || fieldJSONTransRequests.contains(RequestType.All);
            boolean fieldId = fieldJSONId != null;
            boolean fieldReadOnly = fieldJSONReadOnly != null || fieldId;
            boolean fieldValue = fieldXmlValue != null;

            Method getter = getterForName(c, property);
            XmlElement methodXmlEle = getter == null ? null : getter.getAnnotation(XmlElement.class);
            XmlAttribute methodXmlAttr = field == null ? null : getter.getAnnotation(XmlAttribute.class);
            XmlValue methodXmlValue = getter == null ? null : getter.getAnnotation(XmlValue.class);
            JSONElement methodJSONEle = getter == null ? null : getter.getAnnotation(JSONElement.class);
            XmlTransient methodXmlTrans = getter == null ? null : getter.getAnnotation(XmlTransient.class);
//            JSONTransient methodJSONTrans = method == null ? null : method.getAnnotation(JSONTransient.class);
//            Set<RequestType> methodJSONTransRequests = methodJSONTrans == null ? Collections.EMPTY_SET :
//                EnumSet.copyOf(Arrays.asList(methodJSONTrans.request()));
            JSONReadOnly methodJSONReadOnly = getter == null ? null : getter.getAnnotation(JSONReadOnly.class);
            JSONId methodJSONId = getter == null ? null : getter.getAnnotation(JSONId.class);
            JSONFormat methodJSONFormat = field == null ? null : field.getAnnotation(JSONFormat.class);
            boolean methodEle = methodJSONEle != null || methodXmlEle != null
                    || methodXmlAttr != null || methodXmlValue != null;
            boolean methodTrans = methodXmlTrans != null;// || methodJSONTransRequests.contains(RequestType.All);
            boolean methodId = methodJSONId != null;
            boolean methodReadOnly = methodJSONReadOnly != null || methodId;
            boolean methodValue = methodXmlValue != null;

            if ((fieldTrans && ! methodEle) ||
                    (methodTrans && ! fieldEle)) {
                /**
                 * is transient
                 */
                continue;
            }

            if (fieldEle && methodEle) {
                throw new IllegalArgumentException();
            }

            String name;
            Type type;
//            TypeMeta.Accessor<Object, Object, Object> accessor;
//            Collection<RequestType> transientFor = Collections.EMPTY_LIST;
            boolean readOnly;
            boolean id;
            boolean fieldBased;
            boolean isValue;
            String mapKey;
            AccessorType accessorType;
            Method setter = null;
            String format;
            JSONFormat[] formats;
            if (fieldEle) {
                name = name(property, fieldXmlEle, fieldJSONEle);
                fieldBased = true;
            } else if (methodEle) {
                name = name(property, methodXmlEle, methodJSONEle);
                fieldBased = false;
            } else if (field != null) {
                name = property;
                fieldBased = true;
            } else if (getter != null) {
                name = property;
                fieldBased = false;
            } else {
                throw new IllegalArgumentException();
            }

            if (fieldBased) {
                type = field.getGenericType();
                readOnly = fieldReadOnly;
                id = fieldId;
                mapKey = fieldJSONEle == null ? null : fieldJSONEle.mapKey();
                isValue = fieldValue;
                format = fieldJSONEle == null ? null : fieldJSONEle.format();
                formats = fieldJSONFormat == null ? null : new JSONFormat[] { fieldJSONFormat };
            } else {
                type = getter.getGenericReturnType();
                setter = setterForName(c, name, getter.getReturnType());
                readOnly = methodReadOnly;
                id = methodId;
                mapKey = methodJSONEle == null ? null : methodJSONEle.mapKey();
                isValue = methodValue;
                format = methodJSONEle == null ? null : methodJSONEle.format();
                formats = methodJSONFormat == null ? null : new JSONFormat[] { methodJSONFormat };
            }
            if (mapKey != null && JSONElement.DEFAULT.equals(mapKey)) {
                mapKey = null;
            }
            if (format != null && JSONElement.DEFAULT.equals(format)) {
                format = null;
            }
            if (formats == null) {
                formats = new JSONFormat[0];
            }

            Class rawType = Helpers.getRaw(type);
            RelationType relation;
            Class eleType;
            if (List.class.isAssignableFrom(rawType)) {
                relation = RelationType.Collection;
                accessorType = AccessorType.List;
                eleType = (Class) Helpers.getTypes(type, Collection.class)[0];
            } else if (Collection.class.isAssignableFrom(rawType)) {
                relation = RelationType.Collection;
                accessorType = AccessorType.Collection;
                eleType = (Class) Helpers.getTypes(type, Collection.class)[0];
            } else if (rawType.isArray()) {
                relation = RelationType.Collection;
                accessorType = AccessorType.Array;
                eleType = rawType.getComponentType();
            } else {
                relation = RelationType.Single;
                accessorType = AccessorType.Single;
                eleType = rawType;
            }

            TypeMeta.Accessor<Object, Object, Object> accessor;
            if (fieldBased) {
                accessor = fieldAccessor(field, accessorType);
            } else {
                accessor = methodAccessor(getter, setter, accessorType);
            }
            switch (accessorType) {
                case List:
                    accessor = listAccessor((TypeMeta.Accessor) accessor);
                    break;
                case Single:
                    break;
                default:
                    throw new IllegalArgumentException(accessorType.name());
            }

            TypeMeta sub = meta.get(eleType);
            if (sub == null) {
                throw new IllegalArgumentException(type.toString());
            }
            if (format != null) {
                sub = metaForFormat(sub, format);
            }
            if (relation == RelationType.Collection && mapKey != null) {
                relation = RelationType.Map;
                accessor = mapKeyAccessor((TypeMeta.Accessor) accessor, mapKey, (TypeMeta.Complex<Object>) sub);
                sub = metaWithout((TypeMeta.Complex<Object>) sub, mapKey);
            } else {
                mapKey = null;
            }

            m.addProperty(name, sub, relation, accessor, readOnly, id, isValue, formats);
        }
    }

//    private void unwrapProperties(TypeMeta<Object> m) {
//        if (m.isPrimitive()) {
//            return;
//        }
//
//        TypeMeta.Complex<Object> c = (TypeMeta.Complex<Object>) m;
//        for (TypeMeta.Property<Object, Object, Object> p : c.getProperties()) {
//            if (p.getType().isPrimitive()) {
//                continue;
//            }
//
//            TypeMeta.Complex<Object> sub = (TypeMeta.Complex<Object>) p.getType();
//            if (sub.getProperties().size() == 1 && sub.getProperties().iterator().next().isValue()) {
//
//            }
//        }
//    }

    private TypeMeta<Object> metaForFormat(final TypeMeta<Object> meta, final String format) {
        if (meta.isPrimitive()) {
            return meta;
        }

//        final Set<String> trans = new HashSet<String>();
//        String value = null;
//
        final TypeMeta.Complex<Object> c = (TypeMeta.Complex<Object>) meta;
//        for (Property<Object, Object, Object> p : c.getProperties()) {
//            TypeMeta.PropertyFlags flags = p.getFormat(format);
//            if (flags.isTrans()) {
//                trans.add(p.getName());
//            }
//            if (flags.isValue()) {
//                if (value != null) {
//                    throw new IllegalArgumentException();
//                }
//                value = p.getName();
//            }
//        }
//
//        final String finalValue = value;

        return new TypeMeta.ComplexWrapper<Object>(c) {

            private Set<TypeMeta.Property<Object, Object, Object>> properties = null;

            @Override
            public Collection<TypeMeta.Property<Object, Object, Object>> getProperties() {
                if (properties == null) {
                    properties = new HashSet<TypeMeta.Property<Object, Object, Object>>(c.getProperties());
                    Set<TypeMeta.Property<Object, Object, Object>> add =
                            new HashSet<Property<Object, Object, Object>>();
                    for (Iterator<Property<Object, Object, Object>> it = properties.iterator();
                            it.hasNext();) {
                        final Property<Object, Object, Object> p = it.next();
                        if (p.getFormat(format).isTrans()) {
                            it.remove();
                        } else if (p.getFormat(format).isValue()) {
                            it.remove();
                            add.add(new PropertyWrapper<Object, Object, Object>(p) {

                                @Override
                                public PropertyFlags getFlags() {
                                    return p.getFormat(format);
                                }

                            });
                        }
                    }
                    properties.addAll(add);
                }

                return properties;
            }

            @Override
            public Property<Object, Object, Object> getProperty(String name) {
                Property<Object, Object, Object> p = c.getProperty(name);
                return p.getFormat(format).isTrans() ? null : p;
            }

            @Override
            public String[] getModifiers() {
                String[] modifiers = super.getModifiers();
                Set<String> result = new HashSet<String>(Arrays.asList(modifiers));
                result.add(format);
                return result.toArray(new String[0]);
            }

        };
    }

    private TypeMeta<Object> metaWithout(final TypeMeta.Complex<Object> meta, final String property) {
        return new TypeMeta.ComplexWrapper<Object>(meta) {

            private Set<TypeMeta.Property<Object, Object, Object>> properties = null;

            @Override
            public Collection<TypeMeta.Property<Object, Object, Object>> getProperties() {
                if (properties == null) {
                    properties = new HashSet<TypeMeta.Property<Object, Object, Object>>(meta.getProperties());
                    boolean found = false;
                    for (Iterator<Property<Object, Object, Object>> it = properties.iterator();
                            it.hasNext();) {
                        Property<Object, Object, Object> p = it.next();
                        if (property.equals(p.getName())) {
                            it.remove();
                            found = true;
                            break;
                        }
                    }
                    if (! found) {
                        throw new IllegalArgumentException(property);
                    }
                }

                return properties;
            }

            @Override
            public Property<Object, Object, Object> getProperty(String name) {
                return name.equals(property) ? null : meta.getProperty(name);
            }

        };
    }

    private static enum AccessorType {
        Single, Collection, List, Array
    }

    private static class KeyCollection extends AbstractCollection<Integer> {

        private final int size;
        private int pos = -1;

        public KeyCollection(int size) {
            this.size = size;
        }

        @Override
        public Iterator<Integer> iterator() {
            return new Iterator<Integer>() {

                public boolean hasNext() {
                    return pos < (size - 1);
                }

                public Integer next() {
                    if (! hasNext()) {
                        throw new NoSuchElementException();
                    }
                    return ++pos;
                }

                public void remove() {
                    throw new UnsupportedOperationException("Not supported.");
                }

            };
        }

        @Override
        public int size() {
            return size;
        }

    }

    private TypeMeta.Accessor<Object, Object, Object> listAccessor(final TypeMeta.Accessor<Object, Object, List> accessor) {
        return (TypeMeta.Accessor) new TypeMeta.Accessor<Object, Integer, Object>() {

            public void set(Object parent, Integer key, Object o) {
                List list = accessor.get(parent, null);
                if (key == null || key >= list.size()) {
                    list.add(o);
                } else {
                    list.set(key, o);
                }
            }

            public Object get(Object parent, Integer key) {
                List list = accessor.get(parent, null);
                try {
                    return list.get(key);
                } catch (IndexOutOfBoundsException e) {
                    throw new NoSuchElementException(key.toString());
                }
            }

            public Collection<Integer> keys(Object parent) {
                List list = accessor.get(parent, null);
                return list == null ? null : new KeyCollection(list.size());
            }

        };
    }

    private TypeMeta.Accessor<Object, Object, Object> mapKeyAccessor(final TypeMeta.Accessor<Object, Integer, Object> listAccessor,
            final String mapKey, final TypeMeta.Complex<Object> subMeta) {
        return (TypeMeta.Accessor) new TypeMeta.Accessor<Object, String, Object>() {

            public void set(Object parent, String key, Object o) {
                int pos = -1;
                for (Integer i : listAccessor.keys(parent)) {
                    if (key.equals(map(parent, i))) {
                        pos = i;
                        break;
                    }
                }
                listAccessor.set(parent, pos == -1 ? null : pos, o);
            }

            public Object get(Object parent, String key) {
                for (Integer i : listAccessor.keys(parent)) {
                    if (key.equals(map(parent, i))) {
                        return listAccessor.get(parent, i);
                    }
                }
                throw new NoSuchElementException(key);
            }

            public Collection<String> keys(Object parent) {
                Collection<Integer> keys = listAccessor.keys(parent);
                if (keys == null) {
                    return null;
                }
                
                Set<String> result = new HashSet<String>();
                for (Integer key : keys) {
                    result.add(map(parent, key));
                }
                return result;
            }

            private String map(Object parent, int key) {
                Object value = listAccessor.get(parent, key);
                String result = (String) subMeta.getProperty(mapKey).getAccessor().get(value, null);
                return result;
            }

        };
    }

    private TypeMeta.Accessor<Object, Object, Object> fieldAccessor(final Field field, final AccessorType type) {
        return (TypeMeta.Accessor) new TypeMeta.Accessor<Object, Integer, Object>() {

            public void set(Object parent, Integer key, Object o) {
                boolean accessible = field.isAccessible();
                field.setAccessible(true);
                try {
                    field.set(parent, o);
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                } finally {
                    field.setAccessible(accessible);
                }
            }

            public Object get(Object parent, Integer key) {
                boolean accessible = field.isAccessible();
                field.setAccessible(true);
                try {
                    return field.get(parent);
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                } finally {
                    field.setAccessible(accessible);
                }
            }

            public Collection<Integer> keys(Object parent) {
                return Collections.singleton(0);
            }

        };
    }

    private TypeMeta.Accessor<Object, Object, Object> methodAccessor(final Method getter,
            final Method setter, final AccessorType type) {
        return (TypeMeta.Accessor) new TypeMeta.Accessor<Object, Integer, Object>() {

            public void set(Object parent, Integer key, Object o) {
                boolean accessible = setter.isAccessible();
                setter.setAccessible(true);
                try {
                    setter.invoke(parent, o);
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                } finally {
                    setter.setAccessible(accessible);
                }
            }

            public Object get(Object parent, Integer key) {
                boolean accessible = getter.isAccessible();
                getter.setAccessible(true);
                try {
                    return getter.invoke(parent);
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                } finally {
                    getter.setAccessible(accessible);
                }
            }

            public Collection<Integer> keys(Object parent) {
                return Collections.singleton(0);
            }

        };
    }

    private String name(String name, XmlElement xmlEle, JSONElement jsonEle) {
        if (jsonEle != null) {
            if (! jsonEle.name().equals(JSONElement.DEFAULT)) {
                return jsonEle.name();
            }
        }
        if (xmlEle != null) {
            if (! xmlEle.name().equals("##default")) {
                return xmlEle.name();
            }
        }
        return name;
    }

    private String nameFromMethod(String method) {
        if (method.startsWith("get")) {
            return method.substring(3, 4).toLowerCase() + method.substring(4);
        } else if (method.startsWith("is")) {
            return method.substring(2, 3).toLowerCase() + method.substring(3);
        }
        throw new IllegalArgumentException(method);
    }

    private Method getterForName(Class c, String name) {
        Method m;
        try {
            m = c.getDeclaredMethod("get" + name.substring(0, 1).toUpperCase() + name.substring(1));
        } catch (NoSuchMethodException ex) {
            try {
                m = c.getDeclaredMethod("is" + name.substring(0, 1).toUpperCase() +
                        name.substring(1));
            } catch (NoSuchMethodException ex1) {
                return null;
            }

            if (m == null || ! m.getReturnType().equals(Boolean.TYPE)) {
                return null;
            }
        }
        return m;
    }

    private Method setterForName(Class c, String name, Class argType) {
        Method m;
        try {
            m = c.getDeclaredMethod("set" + name.substring(0, 1).toUpperCase() + name.substring(1), argType);
            if (! m.getReturnType().equals(Void.TYPE)) {
                return null;
            }
        } catch (NoSuchMethodException ex) {
            return null;
        }
        return m;
    }

    private Collection<Class> findClasses(Class[] base) {
        Set<Class> classes = new HashSet<Class>();
        for (Class c : base) {
            if (Helpers.isSupported(c)) {
                classes.add(c);
            }
        }

        Set<Class> known = new HashSet<Class>();
        for (Class c : classes) {
            findClasses(known, c);
        }

        return known;
    }

    private Class getElementClass(Type type) {
        Class raw = Helpers.getRaw(type);
        if (Collection.class.isAssignableFrom(raw)) {
            return (Class) Helpers.getTypes(type, Collection.class)[0];
        }
        return raw;
    }

    private void findClasses(Collection<Class> known, Class c) {
        if (known.contains(c)) {
            return;
        }
        if (! Helpers.isSupported(c)) {
            return;
        }

        known.add(c);
        if (c.getSuperclass() != null) {
            findClasses(known, c.getSuperclass());
        }
        for (Class i : c.getInterfaces()) {
            findClasses(known, i);
        }

        for (Field field : c.getDeclaredFields()) {
            findClasses(known, getElementClass(field.getGenericType()));
        }
        for (Method method : c.getDeclaredMethods()) {
            if (method.getParameterTypes().length == 0) {
                findClasses(known, getElementClass(method.getGenericReturnType()));
            }
        }
    }

    private Map<Class, Collection<Class>> findSubTypes(Collection<Class> classes) {
        Map<Class, Collection<Class>> subTypes = new HashMap<Class, Collection<Class>>();
        for (Class c : classes) {
            subTypes.put(c, new HashSet<Class>());
        }

        for (Class c : classes) {
            for (Class sub : classes) {
                if (subTypes.get(c).contains(sub)) {
                    continue;
                }

                if (c.isAssignableFrom(sub)) {
                    subTypes.get(c).add(sub);
                    subTypes.get(c).addAll(subTypes.get(sub));
                }
            }
        }

        return subTypes;
    }

    public Marshaller createMarshaller() {
        return new MarshallerImpl(this);
    }

    @Override
    public Unmarshaller createUnmarshaller() {
        return new UnmarshallerImpl(this);
    }

    public Collection<TypeMeta.Complex> getRootTypes() {
        return rootTypes;
    }

    public Collection<TypeMeta.Primitive> getEnumTypes() {
        return enumTypes;
    }

    public Set<TypeMeta.Complex> getComplexTypes() {
        return complexTypes;
    }

    public String toIdentifier(String name) {
        if (escapeNames) {
            return name;
        }

        String[] parts = name.split("-");
        String result = null;
        for (int i = 0; i < parts.length; i++) {
            String part = parts[i];
            if (result == null) {
                result = part;
            } else {
                result += part.substring(0, 1).toUpperCase() + part.substring(1);
            }
        }

        if (result != null) {
            String realResult = "";
            for (int i = 0; i < result.length(); i++) {
                char c = result.charAt(i);
                if (Character.isLetterOrDigit(c)) {
                    realResult += c;
                }
            }
            return realResult;
        }

        return "";
    }

    @Override
    public void setUnwrap(boolean unwrap) {
        this.unwrap = unwrap;
    }

    @Override
    public boolean isUnwrap() {
        return unwrap;
    }

    @Override
    public boolean isEscapeNames() {
        return escapeNames;
    }

    @Override
    public void setEscapeNames(boolean escapeNames) {
        this.escapeNames = escapeNames;
    }

}
